package com.bsj.sipgateway.sip.transmit.cmd.impl;

import com.bsj.power.common.config.exception.ApiException;
import com.bsj.power.common.config.global.JsonResultEnum;
import com.bsj.power.common.config.redis.RedisConstant;
import com.bsj.power.common.def.entity.gb28181.Device;
import com.bsj.power.common.def.entity.gb28181.DeviceChannel;
import com.bsj.power.common.def.entity.log.CmdLog;
import com.bsj.power.common.def.enums.CmdLogEnum;
import com.bsj.power.common.mapper.CmdLogMapper;
import com.bsj.power.common.mapper.DeviceChannelMapper;
import com.bsj.power.common.util.DateUtils;
import com.bsj.sipgateway.cache.DeviceCache;
import com.bsj.sipgateway.cache.InfoCseqCache;
import com.bsj.sipgateway.command.RocketMqCommand;
import com.bsj.sipgateway.common.constant.Constants;
import com.bsj.sipgateway.config.SipConfig;
import com.bsj.sipgateway.sip.bean.VideoStreamSession;
import com.bsj.sipgateway.sip.event.SipSubscribe;
import com.bsj.sipgateway.sip.session.CallIdManager;
import com.bsj.sipgateway.sip.session.CommandManager;
import com.bsj.sipgateway.sip.session.VideoStreamSessionManager;
import com.bsj.sipgateway.sip.transmit.cmd.ISIPCommander;
import com.bsj.sipgateway.sip.transmit.cmd.SIPRequestHeaderProvider;
import com.bsj.sipgateway.util.*;
import com.bsj.sipgateway.web.service.IDeviceService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import redis.clients.bsj.JedisClusterBSJ;

import javax.sip.*;
import javax.sip.address.SipURI;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Request;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author bsj-chenjunkang
 * @Description:设备能力接口，用于定义设备的控制、查询能力
 * @date 2022/8/31
 */
@Slf4j
@Component
public class SIPCommander implements ISIPCommander, DisposableBean {

    @Autowired
    private SipConfig sipConfig;

    @Autowired
    private SIPRequestHeaderProvider headerProvider;

    @Autowired
    private SipProvider udpSipProvider;

    @Autowired
    private VideoStreamSessionManager streamSession;

    @Autowired
    private MediaPortUtils mediaPortUtils;

    @Autowired
    @Qualifier("deviceServiceImpl")
    private IDeviceService iDeviceService;

    @Autowired
    private JedisClusterBSJ jedisClusterBSJ;

    @Autowired
    private DeviceChannelMapper deviceChannelMapper;

    @Autowired
    private CmdLogMapper cmdLogMapper;


    private static AtomicLong addIncrement = new AtomicLong();


    /**
     * 请求预览视频流（直播）
     *
     * @param deviceId      国标设备id
     * @param channelSerial 逻辑通道号
     * @param resolution    分辨率
     */
    @Override
    public void playStreamCmd(String deviceId, Integer channelSerial, Integer resolution) {
        try {
            Device device = DeviceCache.getDevice(deviceId);
            if (device == null) {
                log.warn("发送直播指令时设备不在线，deviceId:{}", deviceId);
                return;
            }

            Long did = device.getId(); // 设备数据库id
            if (did == null) {
                Device device1 = iDeviceService.selectByDeviceId(deviceId);
                if (device1 == null) {
                    log.warn("下发直播指令时，数据库没有这台设备：deviceId={}", deviceId);
                    return;
                }
                did = device1.getId();
            }

            // 读取redis中的直播的状态
            String hget = jedisClusterBSJ.hget(RedisConstant.VIDEO_PLAY_STATUS, did + "-" + channelSerial);
            if (hget != null && channelSerial.equals(Integer.parseInt(hget))) {
                log.warn("开启直播时，redis已存在直播的状态，不再下发直播指令，deviceId={}，channelSerial={}", deviceId, channelSerial);
                return;
            }
            // 根据设备id和channelId设置ssrc  1/0         车辆id6    通道号3 // ssrc要下发到设备，存放的是车辆的数据库id和逻辑通道号，这两个长度不会超
            String ssrc = SsrcUtil.getPlaySsrc(did, channelSerial + "");
            DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);
            if (deviceChannel == null) {
                log.error("下发直播指令时，数据库没有这个通道信息：deviceId={},channelSerial={}", deviceId, channelSerial);
                return;
            }
            String channelId = deviceChannel.getChannelId();

//            Device dbDevice = iDeviceService.selectById(did);
//            Integer resolution = dbDevice.getResolution();
            // 两个测试数据
//            String ssrc = "0000397001";
//            String channelId = "34020000001320000001";
            // 使用动态udp端口		获取流媒体端口
            // 根据tcp或者udp修改端口
            boolean isTcpWay = false;
            String mediaPort = sipConfig.getMediaUdpPort() + "";
            if (isTcpWay) {
                mediaPort = sipConfig.getMediaTcpPort() + " TCP/";
            }
            //UDP传输模式
            StringBuffer content = new StringBuffer(200);
            content.append("v=0\r\n");
            content.append("o=" + channelId + " 0 0 IN IP4 " + sipConfig.getMediaWanIp() + "\r\n");
            content.append("s=Play\r\n");
            content.append("c=IN IP4 " + sipConfig.getMediaWanIp() + "\r\n");
            content.append("t=0 0\r\n");
            content.append("m=video " + mediaPort + " RTP/AVP 96 98 97\r\n");
            content.append("a=recvonly\r\n");
            content.append("a=rtpmap:96 PS/90000\r\n");
            content.append("a=rtpmap:97 MPEG4/90000\r\n");
            content.append("a=rtpmap:98 H264/90000\r\n");
            content.append("y=" + ssrc + "\r\n");//ssrc

            if (resolution == null) {
                resolution = iDeviceService.selectResolutionByDeviceId(deviceId);
                if (resolution == null) {
                    resolution = 5;
                }
            } else {
                // 打开直播不携带这个参数，用户修改分辨路才带这个参数，带这个参数的话要保存起来
                iDeviceService.updateResolutionByDeviceId(resolution, deviceId);
                log.info("修改这个设备:{}的分辨率为：{},", deviceId, resolution);

            }
            // 配置f字段 f=v/编码格式/分辨率/帧率/码率类型/码率大小，参考28182协议
            // 分辨率：主码流：1080P 子码流：720P
            content.append("f=v/2/" + resolution + "/25/1/1024" + "\r\n");
//            content.append("f=v/2/"+ bitStream +"/25/2/10a/1/8/1" + "\r\n");

            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrc);
            CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
            //根据callId保存deviceId
            CallIdManager.putWithDeviceId(callIdHeader.getCallId(), deviceId);
            ClientTransaction clientTransaction = udpSipProvider.getNewClientTransaction(request);
            CommandManager.put(deviceId, clientTransaction.getBranchId(), UUIDUtil.getUuid(), CommandDownType.REAL_VIDEO_OPEN_CMD, channelSerial + "", null, "0");
            // 记录redis直播打开的状态
            jedisClusterBSJ.hset(RedisConstant.VIDEO_PLAY_STATUS, did + "-" + channelSerial, channelSerial + "");

            // 缓存记录，用于关闭
            VideoStreamSession session = streamSession.putIfAbsent(deviceId, Constants.PLAYER_PREFIX, clientTransaction.getDialog(), ssrc,
                    Integer.parseInt(sipConfig.getMediaUdpPort()), channelSerial + "");
            if (session != null) {
//                log.info("打开视频，deviceId：{}", deviceId);
            } else {
//                log.info("缓存打开视频记录,deviceId:{}", deviceId);
            }
            // 发送
            clientTransaction.sendRequest();
            log.info("已发送打开直播指令，deviceId：{}", deviceId);

            CmdLog cmdLog = new CmdLog();
            cmdLog.setDeviceId(device.getDeviceId());
            cmdLog.setDeviceName(device.getNickName());
            cmdLog.setType(CmdLogEnum.TYPE_PLAY.getDesc());
            cmdLog.setState(CmdLogEnum.STATE_SEND.getDesc());
            cmdLog.setContent(content.toString());
            cmdLog.setTime(new Date());
            cmdLogMapper.insert(cmdLog);

        } catch (ParseException | InvalidArgumentException | SipException e) {
            e.printStackTrace();
        }

    }

    /**
     * 请求回放视频流（回放指令发送给cvr）
     *
     * @param startTime     回放开始时间
     * @param endTime       回放结束时间
     * @param deviceId      国标设备id
     * @param channelSerial 逻辑通道号
     * @param port          回放端口
     */
    @Override
    public void playbackStreamCmd(Long startTime, Long endTime, String deviceId, Integer channelSerial, Integer port) {
        // 根据设备id和逻辑通道号查询真正的通道id
        String channelId;
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);
        if (deviceChannel == null) {
            log.error("[打开回放]没有对应的通道信息，deviceId={}, channelSerial={}", deviceId, channelSerial);
            return;
        } else {
            channelId = deviceChannel.getChannelId();
        }

        // 获取上面通道对应的cvrId，通过cvr管理的设备回放指令要发给cvr
        String nvrId = findNvrId(sipConfig.getCvrIds(), channelId);
        Device nvrDevice = DeviceCache.getDevice(nvrId);
        if (nvrDevice == null) {
            log.warn("[打开回放]nvr设备不在线，nvrId={}", nvrId);
            return;
        }

        // 组装ssrc给流媒体使用
        Device device = iDeviceService.selectByDeviceId(deviceId);
        Long did = device.getId();
        String ssrc = SsrcUtil.getPlayBackSsrc(did, channelSerial + "");

        // 使用动态udp端口		获取流媒体端口
        boolean isTcpWay = false;
        String mediaPort = port + "";
        if (isTcpWay) {
            mediaPort = sipConfig.getMediaTcpPort() + " TCP/";
        }

        StringBuffer content = new StringBuffer(200);
        content.append("v=0\r\n");
        content.append("o=" + channelId + " 0 0 IN IP4 " + sipConfig.getMediaWanIp() + "\r\n");
        content.append("s=Playback\r\n");
        content.append("u=" + channelId + ":0\r\n");
        content.append("c=IN IP4 " + sipConfig.getMediaWanIp() + "\r\n");
        content.append("t=" + startTime + " " + endTime + "\r\n");
        content.append("m=video " + mediaPort + " RTP/AVP 96 97 98 99\r\n");
        content.append("a=recvonly\r\n");
        content.append("a=rtpmap:96 PS/90000\r\n");
        content.append("a=rtpmap:97 MPEG4/90000\r\n");
        content.append("a=rtpmap:98 H264/90000\r\n");
        content.append("a=rtpmap:99 H265/90000\r\n");
        content.append("y=" + ssrc + "\r\n");//ssrc
        String tm = Long.toString(System.currentTimeMillis());

        ClientTransaction clientTransaction = null;
        try {
            String fromTag = "fromplybck" + tm;
            Request request = headerProvider.createPlaybackInviteRequest2(nvrDevice, channelId, content.toString(), null, fromTag, null, ssrc);
            CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
            //根据callId保存deviceId
            String callId = callIdHeader.getCallId();
            CallIdManager.putWithDeviceId(callId, deviceId);

            clientTransaction = udpSipProvider.getNewClientTransaction(request);
            CommandManager.put(deviceId, clientTransaction.getBranchId(), UUIDUtil.getUuid(), CommandDownType.DEV_VOD_MEDIA_TRANS, channelSerial + "", deviceId, "1");
            //缓存记录，用于关闭
            VideoStreamSession session = streamSession.putIfAbsent(deviceId, Constants.PLAY_BLACK_PREFIX, clientTransaction.getDialog(), ssrc,
                    Integer.parseInt(sipConfig.getMediaBackUdpPort()), channelSerial + "");
            if (session != null) {
                log.info("[打开回放]打开回放，diviceId={}，cvrId={}，channelSerial={}, channelId={}"
                        , deviceId, nvrId, channelSerial, channelId);

            } else {
                session = streamSession.get(deviceId, Constants.PLAY_BLACK_PREFIX, channelSerial + "");
                log.info("[打开回放]缓存打开回放，diviceId={}，cvrId={}，channelSerial={}, channelId={}"
                        , deviceId, nvrId, channelSerial, channelId);
            }

            clientTransaction.sendRequest();
        } catch (ParseException | InvalidArgumentException | SipException e) {
            e.printStackTrace();
        }


    }


    /**
     * 根据redis存储的set集合查询通道对应的cvr设备的id
     * cvr管理视频设备的通道
     *
     * @param cvrIds
     * @param channelId
     * @return 默认返回“1”
     */
    private String findNvrId(String cvrIds, String channelId) {
        String nvrId = "1";
        String[] split = cvrIds.split(",");
        for (String cvrId : split) {
            Set<String> smembers = jedisClusterBSJ.smembers(RedisConstant.CVR_DEVICE_CHANNELS_KEY + cvrId);
            if (!CollectionUtils.isEmpty(smembers) && smembers.contains(channelId)) {
                nvrId = cvrId;
                break;
            }
        }
        return nvrId;
    }


    /**
     * 视频流停止
     *
     * @param deviceId      国标设备id
     * @param type          类型，0：直播，1：回放
     * @param channelSerial 逻辑通道号
     */
    @Override
    public synchronized void streamByeCmd(String deviceId, String type, int channelSerial) {
        String logDesc = "";
        if ("0".equals(type)) {
            logDesc = "直播";
        } else if ("1".equals(type)) {
            logDesc = "回放";
        }
        Device device = iDeviceService.selectByDeviceId(deviceId);
        if (device == null) {
            log.error("[关闭{}]没有查询到设备信息，deviceId={}", logDesc, deviceId);
            return;
        }

        // 根据设备id和逻辑通道号查询真正的通道id
        String channelId;
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);
        if (deviceChannel == null) {
            log.error("[关闭{}]没有对应的通道信息，deviceId={}, channelSerial={}", logDesc, deviceId, channelSerial);
            return;
        } else {
            channelId = deviceChannel.getChannelId();
        }

        // 如果是直播，先删除redis中直播通道的状态
        Long did = device.getId();
//        if ("0".equals(type)) {
//            jedisClusterBSJ.hdel(RedisConstant.VIDEO_PLAY_STATUS, did + "-" + channelSerial);
//            log.info("[关闭{}]删除redis中存在的直播状态，did={}, redis-key={}，redis-field={}"
//                    , logDesc, did, RedisConstant.VIDEO_PLAY_STATUS, did + "-" + channelSerial);
//        }

        try {
            // 从缓存中获取会话
            VideoStreamSession videoStreamSession = streamSession.get(deviceId, type, channelSerial + "");
            if (videoStreamSession != null) {
//                log.info("[关闭{}]获取到打开时存入的session信息，session={}", logDesc, videoStreamSession.toString());
                Dialog dialog = videoStreamSession.getDialog();
                if (dialog == null) {
                    return;
                }
                Request byeRequest = null;
                try {
                    byeRequest = dialog.createRequest(Request.BYE);
                } catch (SipException se) {
                    log.error("[关闭{}]发送bye时获取会话失败：device：{}，type：{}，channel：{}", logDesc, deviceId, type, channelId);
                    new Thread(() -> {
                        try {
                            Thread.sleep(5 * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        streamSession.remove(deviceId, type, channelSerial + ""); //发送bye又不响应，直接关不掉了，在这里关
                    }).start();

                    return;
                }
                SipURI byeURI = (SipURI) byeRequest.getRequestURI();
                if (!"1".equals(type)) {
                    String[] address = device.getHostAddress().split(":");
                    byeURI.setHost(address[0]);
                    byeURI.setPort(Integer.parseInt(address[1]));
                } else {
                    // 如果是回放，找对应的cvr的host和port
                    String nvrId = findNvrId(sipConfig.getCvrIds(), channelId);
                    Device deviceNvr = iDeviceService.selectByDeviceId(nvrId);
                    if (deviceNvr != null) {
                        byeURI.setHost(deviceNvr.getIp());
                        byeURI.setPort(deviceNvr.getPort());
//                        log.info("[关闭{}]发送Bye时重新修改byeURI的ip={}，host={}", logDesc, deviceNvr.getIp(), deviceNvr.getPort());
                    }
                }
                ClientTransaction clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
                //把deviceId放入缓存,从from里获取的deviceId不一定正确，一次会话的callId相同。
                CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
                CallIdManager.putWithDeviceId(callIdHeader.getCallId(), deviceId);

                // 删除session
                streamSession.remove(deviceId, type, channelSerial + "");
//                log.info("[关闭{}]删除会话session：deviceId:{},type:{},channelSerial:{}", logDesc, deviceId, type, channelSerial);

                CommandManager.putWithSessionType(deviceId, clientTransaction.getBranchId(), UUIDUtil.getUuid(), Integer.parseInt(type), type, channelSerial + "");
//                log.info("[关闭{}]视频流停止，保存command等待响应bye，deviceId：{}，branch：{}", logDesc, deviceId, clientTransaction.getBranchId());

                //向设备发送bye
                dialog.sendRequest(clientTransaction);

                if ("0".equals(type)) {
                    log.info("关闭直播，向设备发送bye，等待设备回复ok后再删除redis的直播状态，Call-ID={},deviceId={}", callIdHeader.getCallId(), deviceId);
                    // 保存redis，等待回复成功以后再删除这个jedisClusterBSJ.hdel(RedisConstant.VIDEO_PLAY_STATUS, did + "-" + channelSerial);
                    jedisClusterBSJ.set(RedisConstant.WAITING_REPLY_CALLID + callIdHeader.getCallId(), deviceId + "_" + did + "_" + channelSerial);
                    jedisClusterBSJ.expire(RedisConstant.WAITING_REPLY_CALLID + callIdHeader.getCallId(), 300);
                }

                InfoCseqCache.CSEQCACHE.remove(channelId);
//                log.info("[关闭{}]已发送bye请求，deviceId:{}", logDesc, deviceId);
                new Thread(() -> {
                    try {
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    streamSession.remove(deviceId, type, channelId); //发送bye又不响应，直接关不掉了，在这里关
                }).start();

                // 指令收发记录
                CmdLog cmdLog = new CmdLog();
                cmdLog.setDeviceId(device.getDeviceId());
                cmdLog.setDeviceName(device.getNickName());
                String desc = null;

                if ("0".equals(type)) {
                    desc = CmdLogEnum.TYPE_PLAY_CLOSE.getDesc();
                    cmdLog.setType(desc);
                } else if ("1".equals(type)) {
                    desc = CmdLogEnum.TYPE_PLAY_BACK_CLOSE.getDesc();
                    cmdLog.setType(desc);
                }
                cmdLog.setState(CmdLogEnum.STATE_SEND.getDesc());
                cmdLog.setContent(byeRequest.toString());
                cmdLog.setTime(new Date());
                cmdLogMapper.insert(cmdLog);

            } else {
                log.warn("没有需要关闭的会话session,deviceId:{}", deviceId);
                if ("0".equals(type)) {
                    log.info("再次清除redis中直播的状态信息，key：{}，field：{}", RedisConstant.VIDEO_PLAY_STATUS, did + "-" + channelSerial);
                    jedisClusterBSJ.hdel(RedisConstant.VIDEO_PLAY_STATUS, did + "-" + channelSerial);
                }

            }
        } catch (Exception e) {
            log.error("发送bye关闭会话异常", e);
        }
    }

    /**
     * 更新直播通道信息
     *
     * @param deviceId      国标设备id
     * @param type          类型 0是直播 1是回放
     * @param channelSerial 逻辑通道号
     */
    @Override
    public void updatePlayStreamState(String deviceId, String type, Integer channelSerial) {

        try {
            Device device = DeviceCache.getDevice(deviceId);
            if (device == null) {
                log.warn("关闭视频时设备不在线，deviceId={}", deviceId);
                return;
            }
            Long did = device.getId();
            if (did == null) {
                Device devicedb = iDeviceService.selectByDeviceId(deviceId);
                if (devicedb == null) {
                    log.warn("手动清除直播通道时，没有查询到这台设备，deviceId={}", deviceId);
                    return;
                } else {
                    did = devicedb.getId();
                }
            }

            // 如果是直播，就删除redis中直播通道的状态
            if (Constants.PLAYER_PREFIX.equals(type)) {
                jedisClusterBSJ.hdel(RedisConstant.VIDEO_PLAY_STATUS, did + "-" + channelSerial);
                log.info("删除缓存中存在的直播状态，did:{}, type:{}", did, type);
            }


            DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);
            if (deviceChannel == null) {
                log.warn("更新通道信息时没有获取到通道信息，deviceId={},channelSerial={}", deviceId, channelSerial);
                return;
            }
            String channelId = deviceChannel.getChannelId();
            // 从缓存中获取会话
            VideoStreamSession videoStreamSession = streamSession.get(deviceId, type, channelSerial + "");
            if (videoStreamSession != null) {
                log.info("关闭视频时，获取到session，deviceId:{}，type：{}，channelSerial：{}，session:{}", deviceId, type, channelSerial, videoStreamSession);
//                log.info("停止视频dialog：" + videoStreamSession.getDialog());
                Dialog dialog = videoStreamSession.getDialog();
                if (dialog == null) {
                    return;
                }
                Request byeRequest = null;
                try {
                    byeRequest = dialog.createRequest(Request.BYE);
                } catch (SipException se) {
                    log.error("发送bye时获取会话失败：device：{}，type：{}，channel：{}", deviceId, type, channelId);
                    new Thread(() -> {
                        try {
                            Thread.sleep(5 * 1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        streamSession.remove(deviceId, type, channelSerial + ""); //发送bye又不响应，直接关不掉了，在这里关
                    }).start();

                    return;
                }
                SipURI byeURI = (SipURI) byeRequest.getRequestURI();
                if (!"1".equals(type)) {
                    String[] address = device.getHostAddress().split(":");
                    byeURI.setHost(address[0]);
                    byeURI.setPort(Integer.parseInt(address[1]));
                }
                ClientTransaction clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
                //把deviceId放入缓存,从from里获取的deviceId不一定正确，一次会话的callId相同。
                CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
                CallIdManager.putWithDeviceId(callIdHeader.getCallId(), deviceId);

                // 删除session
                streamSession.remove(deviceId, type, channelSerial + "");
                log.info("删除会话session：deviceId:{},type:{},channelSerial:{}", deviceId, type, channelSerial);

                CommandManager.putWithSessionType(deviceId, clientTransaction.getBranchId(), UUIDUtil.getUuid(), Integer.parseInt(type), type, channelSerial + "");
                log.info("视频流停止，保存command等待响应bye，deviceId：{}，branch：{}", deviceId, clientTransaction.getBranchId());

                //向设备发送bye
                dialog.sendRequest(clientTransaction);
                InfoCseqCache.CSEQCACHE.remove(channelId);
                log.info("已发送bye请求，deviceId:{}，关闭类型type：{}", deviceId, type);
                String finalChannelId = channelId;
                new Thread(() -> {
                    try {
                        Thread.sleep(5 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    streamSession.remove(deviceId, type, finalChannelId); //发送bye又不响应，直接关不掉了，在这里关
                }).start();


            } else {
                log.warn("没有需要关闭的会话session,deviceId:{}", deviceId);

            }
        } catch (Exception e) {
            log.error("发送bye关闭会话异常", e);
        }
    }


    @Override
    public void destroy() throws Exception {
        log.info("程序关闭，向所有设备发送bye");
        ConcurrentHashMap<String, VideoStreamSession> allMap = streamSession.getAllMap();
        for (String key : allMap.keySet()) {

            try {
                VideoStreamSession session = allMap.get(key);
                Dialog dialog = session.getDialog();
                Request byeRequest = dialog.createRequest(Request.BYE);
                SipURI byeURI = (SipURI) byeRequest.getRequestURI();

                String deviceId = key.split("_")[0];
                Device device = DeviceCache.getDevice(deviceId);
                String[] address = device.getHostAddress().split(":");
                byeURI.setHost(address[0]);
                byeURI.setPort(Integer.parseInt(address[1]));
                ClientTransaction clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
                //把deviceId放入缓存,从from里获取的deviceId不一定正确，一次会话的callId相同。
                CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
                CallIdManager.putWithDeviceId(callIdHeader.getCallId(), deviceId);
                //向设备发送bye
                log.info("向设备：{}发送bye", deviceId);
                dialog.sendRequest(clientTransaction);

            } catch (Exception e) {
                log.error("发送{}的bye请求失败，", key);
            }
        }
        log.info("发送bye结束");
    }


    private ClientTransaction transmitRequest(Device device, Request request) throws SipException {
        return transmitRequest(device, request, null, null);
    }


    private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException {
        ClientTransaction clientTransaction = udpSipProvider.getNewClientTransaction(request);

        clientTransaction.sendRequest();
        return clientTransaction;
    }


    @Override
    public boolean deviceInfoQuery(Device device) {
        return false;
    }

    @Override
    public boolean catalogQuery(Device device) {
        if (device == null) {
            log.info("下发catalogQuery时没有获取到对应的cvr的缓存信息");
            return false;
        }

        try {
            StringBuffer catalogXml = new StringBuffer(200);
            catalogXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
            catalogXml.append("<Query>\r\n");
            catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
            catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
            catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            catalogXml.append("</Query>\r\n");

            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCat" + tm, null);
//            log.info("发送Catalog到设备：" + device.getDeviceId());
            transmitRequest(device, request);
        } catch (SipException | ParseException | InvalidArgumentException e) {
            log.error("发送Catalog异常，deviceId:{}，", device.getDeviceId(), e);
            return false;
        }
        return true;
    }


    @Override
    public boolean recordInfoQuery(String deviceId, int channelSerial, String startTime, String endTime) {
        // 根据设备id和逻辑通道号查询真正的通道id
        String channelId;
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);
        if (deviceChannel == null) {
            log.error("[请求回放资源列表]没有对应的通道信息，deviceId={}, channelSerial={}", deviceId, channelSerial);
            return false;
        } else {
            channelId = deviceChannel.getChannelId();
        }

        // 获取上面通道对应的cvrId，通过cvr管理的设备回放指令要发给cvr
        String nvrId = findNvrId(sipConfig.getCvrIds(), channelId);
        Device nvrDevice = DeviceCache.getDevice(nvrId);
        if (nvrDevice == null) {
            log.warn("[请求回放资源列表]nvr设备不在线，nvrId={}", nvrId);
            throw new ApiException(JsonResultEnum.CVR_DEVICE_OFFLINE);
        }
        try {

            if (addIncrement.get() >= Long.MAX_VALUE) {
                addIncrement = new AtomicLong();
            }
            String sn = "" + addIncrement.incrementAndGet();

            // 直接按照空格分开中间加上“T”
            String[] beginTimeArr = startTime.split(" ");
            String[] endTimeArr = endTime.split(" ");


            StringBuffer recordInfoXml = new StringBuffer(200);
            recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
            recordInfoXml.append("<Query>\r\n");
            recordInfoXml.append("<CmdType>RecordInfo</CmdType>\r\n");
            recordInfoXml.append("<SN>").append(sn).append("</SN>\r\n");
            recordInfoXml.append("<DeviceID>").append(channelId).append("</DeviceID>\r\n");
            recordInfoXml.append("<StartTime>").append(beginTimeArr[0]).append("T").append(beginTimeArr[1]).append("</StartTime>\r\n");
            recordInfoXml.append("<EndTime>").append(endTimeArr[0]).append("T").append(endTimeArr[1]).append("</EndTime>\r\n");
//            recordInfoXml.append("<Secrecy>0</Secrecy>\r\n");
            // 大华NVR要求必须增加一个值为all的文本元素节点Type
            recordInfoXml.append("<Type>all</Type>\r\n");
            recordInfoXml.append("</Query>\r\n");

            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest2(nvrDevice, channelId, recordInfoXml.toString(), "ViaRecordInfoBranch", "fromRec" + tm, null);
            //发送请求
            transmitRequest(nvrDevice, request);
            log.info("[请求回放资源列表]已成功下发录像查询指令：deviceId={}，nvrId={}，channelSerial={}，channelId={}"
                    , deviceId, nvrId, channelSerial, channelId);
            //记录mq命令用于响应        //这里响应的deviceId，channelId充满了随机性，不能作为判断标准。采用自增的sn来做标志
            CommandManager.put("RecordInfo", sn, UUIDUtil.getUuid(), CommandDownType.DEV_VOD_MEDIA_QUERY, channelSerial + "", deviceId, null);

            // 指令收发记录
            CmdLog cmdLog = new CmdLog();
            cmdLog.setDeviceId(nvrDevice.getDeviceId());
            cmdLog.setDeviceName(nvrDevice.getNickName());
            cmdLog.setTime(new Date());
            String desc = CmdLogEnum.TYPE_RECORD_QUERY.getDesc();
            cmdLog.setType(desc);
//            log.info("[" + desc + "]，下发录像查询指令，查询的deviceId={}，下发的deviceId={}" + deviceId, nvrDevice.getDeviceId());
            cmdLog.setState(CmdLogEnum.STATE_SEND.getDesc());
            cmdLog.setContent("下发录像查询指令：" + request.toString());
            cmdLogMapper.insert(cmdLog);

        } catch (Exception e) {
            log.error("[请求回放资源列表]查询历史视频记录异常", e);
            return false;
        }
        return true;
    }


    @Override
    public void broadcastStreamCmd(String deviceId, RocketMqCommand command) {
        Device device = DeviceCache.getDevice(deviceId);
        if (device == null) {
            //设备不在线，mq响应下发失败
            command.setStatus(0);
            command.setRetMsg("设备不在线");
            return;
        } else {
            command.setStatus(2);
        }
        Long did = device.getId();
        if (CommandManager.getWithBroadcast(deviceId) == null && !streamSession.contain(deviceId, Constants.BROADCAST_PREFIX, command.getSpareMessage())) {
            try {
                //缓存uuid
                CommandManager.putWithBroadcast(deviceId, command.getUuid(), command.getCmdType(), command.getSpareMessage(), null);
                StringBuffer broadcastXml = new StringBuffer(200);
                broadcastXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
                broadcastXml.append("<Notify>\r\n");
                broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
                broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
                broadcastXml.append("<SourceID>" + sipConfig.getSipId() + "</SourceID>\r\n");
                broadcastXml.append("<TargetID>" + deviceId + "</TargetID>\r\n");
                broadcastXml.append("</Notify>\r\n");
                String tm = Long.toString(System.currentTimeMillis());
                Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "ViaBroadcastBranch", "FromBro" + tm, null);
                transmitRequest(device, request);
            } catch (Exception e) {
                log.error("发送Message的broadcast命令异常，deviceId:{}，", deviceId, e);
                //发送mq
                command.setStatus(0);
                command.setRetMsg("发送语音广播的Message命令错误");
            }
        } else {
            command.setStatus(0);
            command.setRetMsg("设备已有语音广播中");
        }
    }


    /**
     * 订阅、取消订阅移动位置
     *
     * @param device   视频设备
     * @param expires  订阅超时时间
     * @param interval 上报时间间隔
     * @return true = 命令发送成功
     */
    public boolean mobilePositionSubscribe(Device device, int expires, int interval) {
        try {
            StringBuffer subscribePostitionXml = new StringBuffer(200);
            subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
            subscribePostitionXml.append("<Query>\r\n");
            subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
            subscribePostitionXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
            subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
            if (expires > 0) {
                subscribePostitionXml.append("<Interval>" + interval + "</Interval>\r\n");
            }
            subscribePostitionXml.append("</Query>\r\n");
            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, expires, "presence"); //Position;id=" + tm.substring(tm.length() - 4));
            transmitRequest(device, request);
            return true;
        } catch (Exception e) {
            log.error("发送移动位置订阅异常", e);
            return false;
        }
    }


    /**
     * 前端控制，包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
     *
     * @param deviceId      控制设备国标id
     * @param channelSerial 预览通道序列号
     * @param cmdCode       指令码
     * @param parameter1    默认0
     * @param parameter2    这个参数是预置位编号默认1
     * @param combineCode2  默认0
     * @throws SipException
     * @throws InvalidArgumentException
     * @throws ParseException
     */
    @Override
    public void frontEndCmd(String deviceId, int channelSerial, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException {
        Device device = DeviceCache.getDevice(deviceId);
        if (device == null) {
            log.warn("发送远程启动命令时设备不在线，deviceId={}", deviceId);
            throw new ApiException(JsonResultEnum.DEVICE_OFFLINE);
        }
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);

        String ptzCmd = frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
        StringBuffer ptzXml = new StringBuffer(200);
        ptzXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
        ptzXml.append("<Control>\r\n");
        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
        ptzXml.append("<DeviceID>" + deviceChannel.getChannelId() + "</DeviceID>\r\n");
        ptzXml.append("<PTZCmd>" + ptzCmd + "</PTZCmd>\r\n");
        ptzXml.append("<Info>\r\n");
        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
        ptzXml.append("</Info>\r\n");
        ptzXml.append("</Control>\r\n");

        String tm = Long.toString(System.currentTimeMillis());
        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzCmdBranch", "FromPtz" + tm, null);
        log.info("发送DeviceControl到设备：" + device.getDeviceId() + ",ptzCmd:" + ptzCmd);
        transmitRequest(device, request);
    }


    @Override
    public boolean ptz(String command, int moveSpeed, String deviceId, int channelSerial) {
        Device device = DeviceCache.getDevice(deviceId);
        if (device == null) {
            log.warn("下发设备云台控制指令时设备不在线，deviceId={}", deviceId);
            throw new ApiException(JsonResultEnum.DEVICE_OFFLINE);
        }
        try {
            int cmdCode = 0;
            switch (command) {
                case "left":
                    cmdCode = 2;
                    break;
                case "right":
                    cmdCode = 1;
                    break;
                case "up":
                    cmdCode = 8;
                    break;
                case "down":
                    cmdCode = 4;
                    break;
//            case "upleft":
//                cmdCode = 10;
//                break;
//            case "upright":
//                cmdCode = 9;
//                break;
//            case "downleft":
//                cmdCode = 6;
//                break;
//            case "downright":
//                cmdCode = 5;
//                break;
                case "zoomPlus":
                    cmdCode = 16;
                    break;
                case "zoomReduce":
                    cmdCode = 32;
                    break;
                case "stop":
                    cmdCode = 0;
                    break;
                default:
                    break;
            }
            String ptzCmd = frontEndCmdString(cmdCode, moveSpeed, moveSpeed, 50);

            String channelId = device.getChannelIdStr().split(",")[channelSerial - 1];
            StringBuffer ptzCmdXml = new StringBuffer(200);
            ptzCmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
            ptzCmdXml.append("<Control>\r\n");
            ptzCmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
            ptzCmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
            ptzCmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
            ptzCmdXml.append("<PTZCmd>" + ptzCmd + "</PTZCmd>\r\n");
            ptzCmdXml.append("</Control>\r\n");

            String tm = Long.toString(System.currentTimeMillis());
            Request request = headerProvider.createMessageRequest(device, ptzCmdXml.toString(), "ViaPtzCmdBranch", "FromPtz" + tm, null);
            log.info("发送DeviceControl到设备：" + device.getDeviceId() + ",ptzCmd:" + ptzCmd);
            transmitRequest(device, request);

        } catch (SipException | ParseException | InvalidArgumentException e) {
            log.error("发送DeviceControl异常，deviceId:{}，", deviceId, e);
            return false;
        }
        return true;
    }

    /**
     * 远程启动控制命令
     *
     * @param deviceId 视频设备id
     */
    @Override
    public void teleBootCmd(String deviceId) {

        Device device = DeviceCache.getDevice(deviceId);
        if (device == null) {
            log.warn("发送远程启动命令时设备不在线，deviceId={}", deviceId);
            throw new ApiException(JsonResultEnum.DEVICE_OFFLINE);
        }

        StringBuffer cmdXml = new StringBuffer(200);
        String charset = "GB2312";
        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
        cmdXml.append("<Control>\r\n");
        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
        cmdXml.append("<DeviceID>" + deviceId + "</DeviceID>\r\n");
        cmdXml.append("<TeleBoot>Boot</TeleBoot>\r\n");
        cmdXml.append("</Control>\r\n");

        Request request = null;
        try {
            request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null);
            transmitRequest(device, request);
        } catch (ParseException | InvalidArgumentException | SipException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void guardCmd(String deviceId, String guardCmdStr) {
        Device device = DeviceCache.getDevice(deviceId);
        if (device == null) {
            log.warn("发送布防/撤防命令时设备不在线，deviceId={}", deviceId);
            throw new ApiException(JsonResultEnum.DEVICE_OFFLINE);
        }
        StringBuffer cmdXml = new StringBuffer(200);
        String charset = "GB2312";
        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
        cmdXml.append("<Control>\r\n");
        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
        cmdXml.append("<DeviceID>" + deviceId + "</DeviceID>\r\n");
        cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
        cmdXml.append("</Control>\r\n");

        Request request = null;
        try {
            request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null);
            transmitRequest(device, request);
        } catch (ParseException | InvalidArgumentException | SipException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void playPauseCmd(Long did, Integer channelSerial) {
        Device device = iDeviceService.selectById(did);
        String deviceId = device.getDeviceId();
        // 根据设备id和逻辑通道号查询真正的通道id
        String channelId;
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);
        if (deviceChannel == null) {
            log.error("[回放暂停]没有对应的通道信息，deviceId={}, channelSerial={}", deviceId, channelSerial);
            return;
        } else {
            channelId = deviceChannel.getChannelId();
        }

        setCseq(channelId);

        VideoStreamSession session1 = streamSession.get(deviceId, Constants.PLAY_BLACK_PREFIX, channelSerial + "");
        if (session1 == null) {
            log.warn("[回放暂停]session不存在，不下发暂停指令：deviceId:{}, type:{},channelSerial:{}"
                    , deviceId, Constants.PLAY_BLACK_PREFIX, channelSerial);
            return;
        }

        StringBuffer content = new StringBuffer(200);
        content.append("PAUSE RTSP/1.0\r\n");
        content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(channelId) + "\r\n");
        content.append("PauseTime: now-\r\n");

        CallIdHeader callId = session1.getDialog().getCallId();
        String localTag = session1.getDialog().getLocalTag(); // fromTag
        String remoteTag = session1.getDialog().getRemoteTag(); // toTag
//        log.info("获取到dialog信息,callId:{}, localTag:{}, remoteTag:{}", callId, localTag, remoteTag);

        String nvrId = findNvrId(sipConfig.getCvrIds(), channelId);
        Device nvrDevice = DeviceCache.getDevice(nvrId);
        playbackControlCmd(nvrDevice, channelId, content.toString(), localTag, remoteTag, callId);
        log.info("[暂停回放]暂停指令已下发，deviceId:{}，cvrId:{}", device.getDeviceId(), nvrId);

    }

    @Override
    public void playResumeCmd(Long did, Integer channelSerial) {
        Device device = iDeviceService.selectById(did);
        String deviceId = device.getDeviceId();
        // 根据设备id和逻辑通道号查询真正的通道id
        String channelId;
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);
        if (deviceChannel == null) {
            log.error("[回放暂停]没有对应的通道信息，deviceId={}, channelSerial={}", deviceId, channelSerial);
            return;
        } else {
            channelId = deviceChannel.getChannelId();
        }

        setCseq(channelId);

        VideoStreamSession session1 = streamSession.get(deviceId, Constants.PLAY_BLACK_PREFIX, channelSerial + "");
        if (session1 == null) {
            log.warn("[回放恢复]session不存在，不下发恢复指令：deviceId:{}, type:{},channelSerial:{}"
                    , deviceId, Constants.PLAY_BLACK_PREFIX, channelSerial);
            return;
        }

        StringBuffer content = new StringBuffer(200);
        content.append("PLAY RTSP/1.0\r\n");
        content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(channelId) + "\r\n");
        content.append("Range: npt=now-\r\n");

        CallIdHeader callId = session1.getDialog().getCallId();
        String localTag = session1.getDialog().getLocalTag(); // fromTag
        String remoteTag = session1.getDialog().getRemoteTag(); // toTag
//        log.info("获取到dialog信息,callId:{}, localTag:{}, remoteTag:{}", callId, localTag, remoteTag);

        String nvrId = findNvrId(sipConfig.getCvrIds(), channelId);
        Device nvrDevice = DeviceCache.getDevice(nvrId);
        playbackControlCmd(nvrDevice, channelId, content.toString(), localTag, remoteTag, callId);
        log.info("[恢复回放]恢复指令已下发，deviceId:{}，cvrId:{}", device.getDeviceId(), nvrId);

    }

    @Override
    public void playSpeedCmd(Long did, Integer channelSerial, Double speed) {
        if (speed != 0.25 && speed != 0.5 && speed != 1 && speed != 2.0 && speed != 4.0) {
            log.warn("不支持的speed： " + speed);
            throw new ApiException(JsonResultEnum.ERROR100);
        }

        Device device = iDeviceService.selectById(did);
        String deviceId = device.getDeviceId();
        // 根据设备id和逻辑通道号查询真正的通道id
        String channelId;
        DeviceChannel deviceChannel = deviceChannelMapper.queryChannelByDeviceIdAndSerial(deviceId, channelSerial);
        if (deviceChannel == null) {
            log.error("[倍速播放]没有对应的通道信息，deviceId={}, channelSerial={}", deviceId, channelSerial);
            return;
        } else {
            channelId = deviceChannel.getChannelId();
        }

        setCseq(channelId);

        VideoStreamSession session1 = streamSession.get(deviceId, Constants.PLAY_BLACK_PREFIX, channelSerial + "");
        if (session1 == null) {
            log.warn("[倍速播放]session不存在，不下发倍速播放指令：deviceId:{}, type:{},channelSerial:{}"
                    , deviceId, Constants.PLAY_BLACK_PREFIX, channelSerial);
            return;
        }

        StringBuffer content = new StringBuffer(200);
        content.append("PLAY RTSP/1.0\r\n");
        content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(channelId) + "\r\n");
        content.append("Scale: " + String.format("%.6f", speed) + "\r\n");

        CallIdHeader callId = session1.getDialog().getCallId();
        String localTag = session1.getDialog().getLocalTag(); // fromTag
        String remoteTag = session1.getDialog().getRemoteTag(); // toTag
//        log.info("获取到dialog信息,callId:{}, localTag:{}, remoteTag:{}", callId, localTag, remoteTag);
        String nvrId = findNvrId(sipConfig.getCvrIds(), channelId);
        Device nvrDevice = DeviceCache.getDevice(nvrId);
        playbackControlCmd(nvrDevice, channelId, content.toString(), localTag, remoteTag, callId);
        log.info("[倍速播放]倍速播放指令已下发，deviceId:{}，cvrId:{}", device.getDeviceId(), nvrId);

    }

    @Override
    public void recordCmd(Long did, String recordCmdStr, Integer channelSerial) {
        StringBuffer cmdXml = new StringBuffer(200);
        Device deviceInDb = iDeviceService.selectById(did);

        Device deviceInCache = DeviceCache.getDevice(deviceInDb.getDeviceId());
        if (deviceInCache == null) {
            throw new ApiException(JsonResultEnum.DEVICE_OFFLINE);
        }
        String channelId = deviceInCache.getChannelIdStr().split(",")[channelSerial - 1];

        String charset = "GB2312";
        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
        cmdXml.append("<Control>\r\n");
        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
        cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
        cmdXml.append("<RecordCmd>" + recordCmdStr + "</RecordCmd>\r\n");
        cmdXml.append("</Control>\r\n");


        Request request = null;
        try {
            request = headerProvider.createMessageRequest(deviceInCache, cmdXml.toString(), null, SipUtils.getNewFromTag(), null);
            transmitRequest(deviceInCache, request);
        } catch (ParseException | InvalidArgumentException | SipException e) {
            e.printStackTrace();
        }
    }

    private void playbackControlCmd(Device device, String channelId, String content, String fromTag, String toTag, CallIdHeader callId) {
        Request request = null;
        try {
            request = headerProvider.createInfoRequest(device, channelId, content.toString(), getNewViaTag(), fromTag, toTag, callId);
            transmitRequest(device, request);
        } catch (ParseException | InvalidArgumentException | SipException e) {
            e.printStackTrace();
        }
    }

    private void playbackControlCmdForScale(Device device, String channelId, String content, String fromTag, String toTag, CallIdHeader callId) {
        Request request = null;
        try {
            request = headerProvider.createInfoRequestForScale(device, channelId, content.toString(), getNewViaTag(), fromTag, toTag, callId);
            transmitRequest(device, request);
        } catch (ParseException | InvalidArgumentException | SipException e) {
            e.printStackTrace();
        }
    }

    public void setCseq(String streamId) {
        if (InfoCseqCache.CSEQCACHE.containsKey(streamId)) {
            InfoCseqCache.CSEQCACHE.put(streamId, InfoCseqCache.CSEQCACHE.get(streamId) + 1);
        } else {
            InfoCseqCache.CSEQCACHE.put(streamId, 2L);
        }
    }

    public static String getNewViaTag() {
        return "z9hG4bK" + RandomStringUtils.randomNumeric(10);
    }

    private int getInfoCseq() {
        return (int) ((Math.random() * 9 + 1) * Math.pow(10, 8));
    }

    /**
     * 云台指令码计算
     *
     * @param cmdCode      指令码：左右、上下、镜头变倍放大缩小
     * @param moveSpeed    水平控制速度 00-FF 0-255
     * @param parameter2   垂直控制速度 00-FF 0-255
     * @param combineCode2 变倍控制速度 0-F 0-15
     */
    public static String frontEndCmdString(int cmdCode, int moveSpeed, int parameter2, int combineCode2) {
        StringBuilder builder = new StringBuilder("A50F01");
        String strTmp;
        strTmp = String.format("%02X", cmdCode);
        builder.append(strTmp, 0, 2);
        strTmp = String.format("%02X", moveSpeed);
        builder.append(strTmp, 0, 2);
        strTmp = String.format("%02X", parameter2);
        builder.append(strTmp, 0, 2);
        //优化zoom变倍速率
        if ((combineCode2 > 0) && (combineCode2 < 16)) {
            combineCode2 = 16;
        }
        strTmp = String.format("%X", combineCode2);
        builder.append(strTmp, 0, 1).append("0");
        //计算校验码
        int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + moveSpeed + parameter2 + (combineCode2 & 0XF0)) % 0X100;
        strTmp = String.format("%02X", checkCode);
        builder.append(strTmp, 0, 2);
        return builder.toString();
    }


    private String getDateBetweenByCmdMessage(String cmdMessage) {
        //头两位为16进制ip长度
        String h = cmdMessage.substring(0, 2);
        int ipLen = new BigInteger(h, 16).intValue();
        //ipLen为字节数,字符串位数需要*2
        ipLen *= 2;
        //ip长度[2]+ip[ipLen*2]+tcp端口号[4]+udp端口号[4]+逻辑通道号[2]+音视频类型[2]+码流类型[2]+存储器[2]+回放方式[2]+回放倍数[2]=22+ipLen*2
        //时间yyMMddHHmmss长度为：12      22+
        String startTimeBcd = cmdMessage.substring(ipLen + 22, ipLen + 22 + 12);
        String endTimeBcd = cmdMessage.substring(ipLen + 22 + 12, ipLen + 22 + 12 + 12);
        Date startTime = DateUtils.strToDate("yyMMddHHmmss", startTimeBcd);
        Date endTime = DateUtils.strToDate("yyMMddHHmmss", endTimeBcd);
        return startTime.getTime() / 1000 + " " + endTime.getTime() / 1000;
    }

}
